#include "StaticWaveSound.h"
#include <string>
#include <iostream>
#include "SoundManager.h"
#include "Sound.h"

namespace UniverseSound
{
	StaticWaveSound::StaticWaveSound(const Ogre::String &name) : SoundClip(name),
		mAudioName(""),
		mAudioFile(0),
		mVorbisInfo(0),
		mVorbisComment(0),
		mPreviousOffset(0),
		mFormatData(0),
		mBuffer(0)
	{
		mStream = false;
		mBufferData.clear();
	}

	StaticWaveSound::~StaticWaveSound()
	{
		_release();
		mAudioFile = 0;
		mVorbisInfo = 0;
		mVorbisComment = 0;
		mBufferData.clear();
		if (mFormatData)
			delete mFormatData;
	}

	void StaticWaveSound::open(Ogre::DataStreamPtr &fileStream)
	{
		char*			sound_buffer=0;
		char			id[5]={0};
		unsigned short	format_tag;
		unsigned long	size;
		int				bytesRead=0;

		mAudioStream = fileStream;
		mAudioName = mAudioStream->getName();
		mAudioStream->read(id, 4);

		if (!strcmp(id, "RIFF"))
		{
			mAudioStream->read(&size, 4);
			mAudioStream->read(id, 4);

			if (!strcmp(id, "WAVE"))
			{
				if (!mFormatData)
					mFormatData = new WavFormatData;

				mAudioStream->read(id, 4);
				mAudioStream->read(&mFormatData->mFormatChunkSize, 4);
				if (mFormatData->mFormatChunkSize >= 16)
				{
					unsigned short extraBytes = mFormatData->mFormatChunkSize-16;
					mAudioStream->read(&format_tag, 2);
					if (format_tag==0x0001 || format_tag==0xFFFE)
					{
						// Read in num channels ( 2 bytes )
						mAudioStream->read(&mFormatData->mNumChannels, 2);

						// Read in sample rate ( 4 bytes )
						mAudioStream->read(&mFormatData->mSampleRate, 4);

						// Read in byte rate ( 4 bytes )
						mAudioStream->read(&mFormatData->mAvgBytesPerSecond, 4);

						// Read in byte align ( 2 bytes )
						mAudioStream->read(&mFormatData->mBlockAlign, 2);

						// Read in bits per sample ( 2 bytes )
						mAudioStream->read(&mFormatData->mBitsPerSample, 2);

						// If WAVEFORMATEXTENSIBLE...
						if (format_tag==0xFFFE)
						{
							unsigned short sigBitsPerSample;
							unsigned short extraInfoSize;

							// Read in significant bits per sample ( 2 bytes )
							mAudioStream->read(&sigBitsPerSample, 2);

							// Read in extra information size ( 2 bytes )
							mAudioStream->read(&extraInfoSize, 2);

							// Read in samples ( 2 bytes )
							mAudioStream->read(&mFormatData->mSamples, 2);

							// Read in channels mask ( 2 bytes )
							mAudioStream->read(&mFormatData->mChannelMask, 2);

							// Read in sub format ( 16 bytes )
							mAudioStream->read(&mFormatData->mSubFormat, sizeof(char[16]));
						}
						// Skip extra info
						else if (extraBytes)
						{
							// Create byte array to hold extra bytes
							char* info = new char[extraBytes];

							// Read in extra bytes chunk
							mAudioStream->read(info, extraBytes);

							// Delete array
							delete [] info;
						}

						// Read in chunk id ( 4 bytes )
						mAudioStream->read(id, 4);

						if (!strcmp(id, "data"))
						{
							try
							{
								// Read in size of audio data ( 4 bytes )
								mAudioStream->read(&mFormatData->mDataSize, 4);

								// Store byte offset of start of audio data
								mFormatData->mAudioOffset = static_cast<unsigned long>(mAudioStream->tell());

								// Allocate array
								sound_buffer = new char[mFormatData->mDataSize];

								// Read entire sound data
								bytesRead = static_cast<int>(mAudioStream->read(sound_buffer, mFormatData->mDataSize));
							}
							catch(...)
							{
								Ogre::LogManager::getSingleton().logMessage("UniverseSound::StaticWaveSound::open(...) - Unable to read the wave data", Ogre::LML_NORMAL);
							}
						}
						else
						{
							// Find "data" chunk
							try
							{
								do
								{
									unsigned short chunkSize;

									// Read in size of chunk data ( 4 bytes )
									mAudioStream->read(&chunkSize, 4);

									// Skip chunk
									mAudioStream->skip(chunkSize);

									// Read next chunk id
									mAudioStream->read(id, 4);
								}
								while (strcmp(id, "data") || mAudioStream->eof());

								// Validity check
								if (!mAudioStream->eof())
								{
									// Read in size of audio data ( 4 bytes )
									mAudioStream->read(&mFormatData->mDataSize, 4);

									// Store byte offset of start of audio data
									mFormatData->mAudioOffset = static_cast<unsigned short>(mAudioStream->tell());

									// Allocate array
									sound_buffer = new char[mFormatData->mDataSize];

									// Read entire sound data
									bytesRead = static_cast<int>(mAudioStream->read(sound_buffer, mFormatData->mDataSize));
								}
								else
								{
									Ogre::LogManager::getSingleton().logMessage("UniverseSound::StaticWaveSound::open(...) - Unable to find wave data", Ogre::LML_NORMAL);
								}
							}
							catch(...)
							{
								Ogre::LogManager::getSingleton().logMessage("UniverseSound::StaticWaveSound::open(...) - Error reading the wave data", Ogre::LML_NORMAL);
							}
						}
					}
					else
					{
						Ogre::LogManager::getSingleton().logMessage("UniverseSound::StaticWaveSound::open(...) - Error: No PCM raw data in wave file - Only uncompressed wave files are supported", Ogre::LML_NORMAL);
					}
				}
				else
				{
					Ogre::LogManager::getSingleton().logMessage("UniverseSound::StaticWaveSound::open(...) - Compressed WAVE files are currently not supported by UniverseSound", Ogre::LML_NORMAL);
				}
			}
			else
			{
				Ogre::LogManager::getSingleton().logMessage("UniverseSound::StaticWaveSound::open(...) - Error: The wave file is invalid", Ogre::LML_NORMAL);
			}
		}
		else
		{
			Ogre::LogManager::getSingleton().logMessage("UniverseSound::StaticWaveSound::open(...) - The specified file is not a valid RIFF Wave file", Ogre::LML_NORMAL);
		}


		// Create OpenAL buffer
		alGetError();
		alGenBuffers(1, &mBuffer);
		if (alGetError() != AL_NO_ERROR)
		{
			Ogre::LogManager::getSingleton().logMessage("UniverseSound::StaticWaveSound::open(...) - Unable to create OpenAL buffer", Ogre::LML_NORMAL);
		}


		// Upload to XRAM buffers if available
		if (SoundManager::getSingleton().isXRAMSupported())
			SoundManager::getSingleton().setXRAMBuffer(1, &mBuffer);
		// Check format support
		if (!_queryBufferInfo())
		{
			Ogre::LogManager::getSingleton().logMessage("UniverseSound::StaticWaveSound::open(...) - The format is not supported by UniverseSound", Ogre::LML_NORMAL);
		}

		// Calculate length in seconds
		mPlayTime = (mFormatData->mDataSize / ((mFormatData->mBitsPerSample/8) * mFormatData->mSampleRate)) / mFormatData->mNumChannels;

		alGetError();
		alBufferData(mBuffer, mFormat, sound_buffer, static_cast<ALsizei>(bytesRead), mFormatData->mSampleRate);
		if (alGetError() != AL_NO_ERROR)
		{
			Ogre::LogManager::getSingleton().logMessage("UniverseSound::StaticWaveSound::open(...) - Unable to load audio data into buffer", Ogre::LML_NORMAL);
		}
		delete [] sound_buffer;
		// Register shared buffer
		SoundManager::getSingleton().registerSharedBuffer(mAudioName, mBuffer);

		// Set ready flag
		mFileOpened = true;
	}

	void StaticWaveSound::open(const Ogre::String &fileName, ALuint &buffer)
	{
		mBuffer = buffer;
		mAudioName = fileName;
		mFileOpened = true;
	}

	bool StaticWaveSound::_queryBufferInfo()
	{
		if (!mFormatData)
			return false;

		switch (mFormatData->mNumChannels)
		{
		case 1:
			{
				if (mFormatData->mBitsPerSample == 8)
				{
					// 8-bit mono
					mFormat = AL_FORMAT_MONO8;
					mBufferSize = mFormatData->mSampleRate/4;
				}
				else
				{
					// 16-bit mono
					mFormat = AL_FORMAT_MONO16;
					mBufferSize = mFormatData->mAvgBytesPerSecond >> 2;
					mBufferSize -= (mBufferSize % mFormatData->mBlockAlign);
				}
			} 
			break;
		case 2:
			{
				if (mFormatData->mBitsPerSample == 8)
				{
					// 8-bit stereo
					mFormat = AL_FORMAT_STEREO8;
					mBufferSize = mFormatData->mSampleRate >> 1;
					mBufferSize -= (mBufferSize % 2);
				}
				else
				{
					// 16-bit stereo
					mFormat = AL_FORMAT_STEREO16;
					mBufferSize = mFormatData->mAvgBytesPerSecond >> 2;
					mBufferSize -= (mBufferSize % mFormatData->mBlockAlign);
				}
			}
			break;
		case 4:
			{
				// 16-bit quad surround sound
				mFormat = alGetEnumValue("AL_FORMAT_QUAD16");
				if (!mFormat)
					return false;
				mBufferSize = mFormatData->mAvgBytesPerSecond >> 2;
				mBufferSize -= (mBufferSize % mFormatData->mBlockAlign);
			}
			break;
		case 6:
			{
				// 16-bit 5.1 surround sound
				mFormat = alGetEnumValue("AL_FORMAT_51CHN16");
				if (!mFormat)
					return false;
				mBufferSize = mFormatData->mAvgBytesPerSecond >> 2;
				mBufferSize -= (mBufferSize % mFormatData->mBlockAlign);
			}
			break;
		case 7:
			{
				// 16-bit 7.1 surround sound
				mFormat = alGetEnumValue("AL_FORMAT_71CHN16");
				if (!mFormat)
					return false;
				mBufferSize = mFormatData->mAvgBytesPerSecond >> 2;
				mBufferSize -= (mBufferSize % mFormatData->mBlockAlign);
			}
			break;
		case 8:
			{
				// 16-bit 8.1 surround sound
				mFormat = alGetEnumValue("AL_FORMAT_81CHN16");
				if (!mFormat)
					return false;
				mBufferSize = mFormatData->mAvgBytesPerSecond >> 2;
				mBufferSize -= (mBufferSize % mFormatData->mBlockAlign);
			}
			break;
		default:
			{
				// No format has been detected!
				Ogre::LogManager::getSingleton().logMessage("UniverseSound - Unable to determine buffer format");
				Ogre::LogManager::getSingleton().logMessage("UniverseSound - Trying to set buffer format to default mono ...");

				mFormat = AL_FORMAT_STEREO16;
				mBufferSize = mFormatData->mAvgBytesPerSecond >> 2;
				mBufferSize -= (mBufferSize % mFormatData->mBlockAlign);
			}
			break;
		}
		return true;
	}

	void StaticWaveSound::_release()
	{
		ALuint source = AL_NONE;
		setSource(source);
		SoundManager::getSingleton().releaseSharedBuffer(mAudioName, mBuffer);
		mPlayPosChanged = false;
		mPlayPos = 0.f;
	}

	void StaticWaveSound::_prebuffer()
	{
		if (mSource == AL_NONE)
			return;

		// Queue buffer
		alSourcei(mSource, AL_BUFFER, mBuffer);
	}

	void StaticWaveSound::setSource(ALuint &source)
	{
		if (source != AL_NONE)
		{
			mSource = source;
			_prebuffer();
			_initSource();
		}
		else
		{
			alSourceStop(mSource);
			alSourcei(mSource, AL_BUFFER, 0);
			mSource = source;
		}
	}

	void StaticWaveSound::play()
	{
		boost::recursive_mutex::scoped_lock l(mMutex);

		if (!mFileOpened)
		{
			if (!mPlayDelayed)
			{
				mPlayDelayed = true;
				SoundManager::getSingletonPtr()->queueThreadedSound(this, ONLOAD_PLAY);
			}
		}

		if (isPlaying())
			return;

		if (mSource == AL_NONE)
		{
			if (!SoundManager::getSingleton().requestSoundSource(this))
			{
				return;
			}
		}

		alSourcePlay(mSource);
		mPlay = true;
		mPlayDelayed = false;
	}

	void StaticWaveSound::pause()
	{
		boost::recursive_mutex::scoped_lock l(mMutex);

		if (!mFileOpened)
		{
			if (!mPauseDelayed)
			{
				mPauseDelayed = true;
				SoundManager::getSingletonPtr()->queueThreadedSound(this, ONLOAD_PAUSE);
			}
		}

		if (mSource == AL_NONE)
			return;

		alSourcePause(mSource);
		mPauseDelayed = false;
	}

	void StaticWaveSound::stop()
	{
		if (!mFileOpened)
		{
			if (!mStopDelayed)
			{
				mStopDelayed = true;
				SoundManager::getSingletonPtr()->queueThreadedSound(this, ONLOAD_STOP);
			}
		}

		if (mSource == AL_NONE)
			return;

		alSourceStop(mSource);
		alSourceRewind(mSource);
		mPlay = false;
		mPreviousOffset = 0;
		mStopDelayed = false;
		if (mGiveUpSource)
		{
			SoundManager::getSingleton().releaseSoundSource(this);
		}
	}

	void StaticWaveSound::loop(bool loop)
	{
		SoundClip::loop(loop);
		if (mSource != AL_NONE)
		{
			alSourcei(mSource, AL_LOOPING, loop);
		}
	}

	void StaticWaveSound::_updateAudioBuffers()
	{
		if (mPlayDelayed)
			play();

		if (mSource == AL_NONE || !mPlay)
			return;

		ALenum state;
		alGetSourcei(mSource, AL_SOURCE_STATE, &state);

		if (state == AL_STOPPED)
		{
			stop();

			if (mFinishedCB && mFinCBEnabled)
			{
				mFinishedCB->execute(static_cast<SoundClip*>(this));
			}
		}
		else
		{
			ALint bytes = 0;
			alGetSourcei(mSource, AL_BYTE_OFFSET, &bytes);

			if (mPreviousOffset > bytes)
			{
				if (mLoopCB && mLoopCBEnabled)
				{
					mLoopCB->execute(static_cast<SoundClip*>(this));
				}
			}

			mPreviousOffset = bytes;
		}
	}
}